package robocup.component.situations;

import java.util.Arrays;

import robocup.component.DAInamiteConstants;
import robocup.component.Prophet;
import robocup.component.SConf;
import robocup.component.Synchro;
import robocup.component.WorldModel;
import robocup.component.ControllerNumbers.PLAY_MODE;
import robocup.component.actionfactories.BasicActionFactory;
import robocup.component.actions.KickAction;
import robocup.component.actions.TackleAction;
import robocup.component.geometry.Vektor;
import robocup.component.worldmodel.PlayersModel;
import robocup.component.worldobjects.Ball;
import robocup.component.worldobjects.Player;
import robocup.component.worldobjects.Player.UPDATE_MODE;

/**
 * This Situation calculates if passing is possible, what teammate to pass to
 * and where to pass to. It uses a simple algorithm:
 * <ol>
 * <li>choose a limited number of pass directions</li>
 * <li>simulate kicks with different power for each direction</li>
 * <li>determine if a teammate gets the simulated ball first and where he gets
 * it</li>
 * <li>calculate the benefit for the resulting passes using the
 * {@link SituationEvaluator} for all successful passes</li>
 * <li>choose the pass with the highest benefit</li>
 * </ol>
 * 
 * Because of all the simulating the pass situation is one of the most time
 * crucial Situations and is therefore very much optimized for efficiency.
 */
public class PassSituation
    extends Situation {

  private final int           emptyInt                      = Integer.MAX_VALUE;

  // the receivers number
  private int           passTo                        = this.emptyInt;

  // the resulting IP
  private final Vektor        resIP                         = new Vektor();

  // the ball resulting from the pass
  private final Ball          resBall                 = new Ball(-1, -1,
                                                          new Vektor(),
                                                          new Vektor());

  // the best pass direction
  private int           bestDir;

  // security in m for pass calculation (teammates)
  double                securityMates                 = 0.35;

  // security in m for pass calculation (opponents)
  double                securityOthers                = -0.1;

  // number of pass directions
  private final int           numberPassDirs                = 120;

  // max. considered cycles
  private final int           maxCycles                     = DAInamiteConstants.PROPHET_MAX_CYCLES_PASSING;

  /**
   * Ref to {@link PlayersModel#getExpectedPlayers()}
   */
  private final Player[]      expectedPlayers;

  /**
   * the body angle for all players
   */
  private final double[]      bodyDirs                      = new double[22];

  /**
   * if false don't pass to this teammate
   */
  private final boolean[]     isFilteredTeammate            = new boolean[11];

  /**
   * try kicking with these percentages of the max kick.
   */
  private final double[]      accelPercentage               = new double[] { 1.0, 0.925, 0.85, 0.6 };

  /**
   * the resulting interception points for each direction.
   */
  private final Vektor[]      interceptPoints               = new Vektor[this.numberPassDirs];

  /**
   * the resulting interception times for each direction.
   */
  private final int[]         interceptTimes                = new int[this.numberPassDirs];

  /**
   * the resulting intercepting player for each direction as number.
   */
  private final int[]         interceptingPlayer            = new int[this.numberPassDirs];

  /**
   * the resulting intercepting opponent intercepting after a teammates. Used
   * for telling how secure the pass is.
   */
  private final int[]         nextInterceptingOpp           = new int[this.numberPassDirs];

  /**
   * the ball position in each cycle by direction
   */
  private final Vektor[]      ballPosition                  = new Vektor[this.numberPassDirs];

  /**
   * the ballspedd in each cycle by direction.
   */
  private final Vektor[]      ballSpeed                     = new Vektor[this.numberPassDirs];

  /**
   * the max. resulting ball speed in each direction
   */
  private final Vektor[]      maxBallSpeed                  = new Vektor[this.numberPassDirs];

  /**
   * the directions to simulate.
   */
  private final Vektor[]      dirs                          = new Vektor[this.numberPassDirs];

  /**
   * the abs. angles of <code>dirs</code>.
   */
  private final double[]      dirAngles                     = new double[this.numberPassDirs];

  /**
   * The distribution of direction.
   * 
   * Based on the line from the ball to the opponents goal each area of 45
   * degree is given a weight. Directions in the opponents goal direction are
   * prefered.
   */
  private final double[]      dirImportance                 = new double[] { 1.0 / 6.0, 1.0 / 6.0, 2.0 / 15.0, 1.0 / 30.0 };

  /**
   * the offset in cycles for each player. used as players that were not seen
   * might have reacted in these cycle.
   */
  private final int[]         playerOffset                  = new int[22];

  /**
   * the maximal turn angles by player turning 1x or 2x. Depends on the players
   * velocity.
   */
  private final double[][]    maxTurnAngle                  = new double[22][2];

  /**
   * the (extrapolated) player positions
   */
  private final Vektor[]      playerPosition                = Vektor
                                                          .getDummyInstances(22);

  /**
   * the (extrapolated) players' speed
   */
  private final Vektor[]      playerSpeed                   = Vektor
                                                          .getDummyInstances(22);

  /**
   * consider the direction for further simulation.
   */
  private final boolean[]     considerDir                   = new boolean[this.numberPassDirs];

  /**
   * filter this direction. not considering it at all.
   */
  private final boolean[]     filteredDirs                  = new boolean[this.numberPassDirs];

  /**
   * the time since last watching the direction.
   */
  private final double[]      dirAge                        = new double[this.numberPassDirs];

  /**
   * the distance between player and ball considered as secure (interception).
   */
  private final double[]      secureDistance                = new double[22];

  /**
   * used for choosing initial directions
   */
  private final int[]         numberDirsLeft                = new int[4];

  /**
   * used for choosing initial directions
   */
  private final int[]         numberDirsRight               = new int[4];

  /**
   * consider a player for a given direction
   */
  private final boolean[][]   considerPlayers               = new boolean[this.numberPassDirs][22];

  /**
   * al angles to players
   */
  private final double[]      angleToPlayer                 = new double[22];

  /**
   * all distances to players
   */
  private final double[]      distanceToPlayer              = new double[22];

  /**
   * avoid heavy Vektor creation for better performance.
   */
  private final Vektor        dummyVektor                   = new Vektor();

  /**
   * the overlap when intercepting. the more overlap the more secure is the pass
   * (teammates)
   */
  private final double[]      interceptDistanceOwn          = new double[this.numberPassDirs];

  /**
   * the overlap when intercepting. the more overlap the more secure is the pass
   * (opponents)
   */
  private final double[]      interceptDistanceOpponent     = new double[this.numberPassDirs];

  /**
   * if we are late we calculate less.
   */
  private final Synchro       synchro;

  /**
   * the resulting interception time.
   */
  private int           resInterceptionTime;

  /**
   * the resulting distance between the interception point and the nearest
   * extrapolated opponent.
   */
  private double        resDistanceOpponent;

  /**
   * the resulting distance between the interception point and the nearest
   * extrapolated teammate.
   */
  private double        resDistanceOwn;

  /**
   * the resulting distance between the interception point and the nearest
   * extrapolated opponent when a teammate intercepts first.
   */
  private double        resNearestOpponentDistance;

  /**
   * the resulting distances between the interception point and the nearest
   * extrapolated opponent when a teammate intercepts first.
   */
  private final double[]      nearestOpponenentDistance     = new double[this.numberPassDirs];

  /**
   * players with more than this angle difference from the pass dir are filtered
   * if they are far
   */
  private static final double MAX_TOLERATED_ANGLE_TO_PLAYER = 90;

  /**
   * near players are not filtered as they may intercept balls in all dirs!
   */
  private static final double MAX_NEAR_DISTANCE_TO_PLAYER   = 4;

  private int bodyCycle = -1;
  
  /**
   * Constructor - a situation, which simulates different possible passes.
   * 
   * @param world -
   *          the current world-model
   * @param prophet -
   *          the predicting device (parent object)
   * @param movements -
   *          a generic movement model
   * @param synchro -
   *          the synchro object, which can be used to estimate the time left
   *          for calculations
   */
  public PassSituation(
      final WorldModel world,
      final Prophet prophet,
      final Movements movements,
      final Synchro synchro) {

    super(world, prophet, movements);

    this.synchro = synchro;

    // we use the next players as our teammate eed one cycle to react
    this.expectedPlayers = this.getWorld().getNextPlayers();
    Arrays.fill(this.considerDir, true);

    // fill arrays
    for (int i = 0; i < this.numberPassDirs; i++) {
      this.interceptPoints[i] = new Vektor();
      this.dirs[i] = new Vektor(1, i * 360.0 / this.numberPassDirs);
      this.ballPosition[i] = new Vektor();
      this.ballSpeed[i] = new Vektor();
      this.maxBallSpeed[i] = new Vektor();
      this.interceptTimes[i] = this.emptyInt;
      this.interceptingPlayer[i] = this.emptyInt;
      this.nextInterceptingOpp[i] = this.emptyInt;
    }
    
    for (int i = 0; i < 22; i++) {

      if (!this.expectedPlayers[i].isGoalie()) {
        this.secureDistance[i] = this.expectedPlayers[i].getKickDistance();
      }
      else {
        this.secureDistance[i] = this.expectedPlayers[i].getCatchDistance();
      }

      if (i < 11) {
        this.secureDistance[i] -= this.securityMates;
      }
      else {
        this.secureDistance[i] -= this.securityOthers;
      }

    }
  }

  /**
   * resets the calculation data.
   * 
   */
  private void reset() {

    for (int i = 0; i < this.numberPassDirs; i++) {
      this.interceptPoints[i].reset();
      this.interceptTimes[i] = this.emptyInt;
      this.interceptingPlayer[i] = this.emptyInt;
      this.nextInterceptingOpp[i] = this.emptyInt;
      this.considerDir[i] = true;

      this.interceptDistanceOpponent[i] = Integer.MAX_VALUE;
      this.interceptDistanceOwn[i] = Integer.MAX_VALUE;
      this.nearestOpponenentDistance[i] = Integer.MAX_VALUE;
    }

    Arrays.fill(this.considerDir, true);
    this.bestAction = null;

    // filter teammates that are offside, goalie, in our penalty area
    for (int i = 0; i < 11; i++) {

      if (this.getPlayer(i).isMe() || this.getPlayer(i).isGoalie() || (this
          .getPlayer(i).getPosition().y > this.getWorld().getOffsideY(true) && this
          .getWorld().getPlayMode() == PLAY_MODE.PLAY_ON) || (this.getWorld()
          .getSelfRef().isGoalie() && this.getWorld().inOwnPenaltyArea(
          this.getPlayer(i)))) {
        this.isFilteredTeammate[i] = true;
      }
      else {
        this.isFilteredTeammate[i] = false;
      }
    }

    
    PLAY_MODE pmode = getWorld().getPlayMode();
    if (pmode == PLAY_MODE.KICK_IN_OWN) {
    	if (getWorld().getBallRef().getPosition().y > -5 &&
    			getWorld().getBallRef().getPosition().y < 35) {
    		for (int i = 0; i < this.isFilteredTeammate.length; i++) {
    			if (i+1 < 9)
    				this.isFilteredTeammate[i] = true;
    			else if (!getWorld().isOxsyKickInPosition()) {
    				
    				this.isFilteredTeammate[i] = true;
    				
    			} else {
    				if (bodyCycle == -1) {
    					this.bodyCycle = getWorld().getBodyCycle();
    					this.isFilteredTeammate[i] = true;
    				} else if (getWorld().getBodyCycle() < this.bodyCycle + 5) {
    					this.isFilteredTeammate[i] = true;
    				} else {
    					// do pass ...
    				}
    			}
    		}
    	}
    	
    	if(pmode == PLAY_MODE.INDIRECT_FREE_KICK_OWN) {
    		for(int i = 0; i < this.isFilteredTeammate.length; i++) {
    			if(i + 1 == 9 || i + 1 == 11) {
    				this.isFilteredTeammate[i] = true;
    			} else {
    				this.isFilteredTeammate[i] = false;
    			}
    		}
    	}
    }
    
    for (int i = 0; i < this.expectedPlayers.length; i++) {
      this.bodyDirs[i] = this.expectedPlayers[i].getBodyDir();
    }

  }

  /**
   * main routine which determines passing and scoring probabilities updating
   * the situation.
   */
  @Override
  protected void update() {

    super.update();

    // reset result data
    this.passTo = this.emptyInt;
    this.resBall.copy(this.getWorld().getBallRef());
    this.benefit = 0.0;
    this.resDistanceOpponent = Integer.MAX_VALUE;
    this.resDistanceOwn = Integer.MAX_VALUE;
    this.resInterceptionTime = Integer.MAX_VALUE;

    // avoid the resource intensive pass calculation when already late,
    // scoring succeeds, or the world is nor VI based.
    if (!this.getWorld().getSelfRef().canKick(this.getWorld().getBallRef()) || !this
        .getWorld().isViBased() || this.synchro.getVIArrivalQuarter() > 2 || this
        .getProphet().getScoreBenefit() > 0.5) {
      if (false && this.synchro.getVIArrivalQuarter() > 2) {
        System.out
            .println(this.getWorld().getCycleAndPlayerString() + " SynchroQuarter:" + this.synchro
                .getVIArrivalQuarter());
      }
      return;
    }

    final Ball b = this.getWorld().getBallRef();
    double accel;
    double bestAccelPercentage = 1;
    double bestValue = this.benefit;

    // set all directions and all data for each direction
    this.setDirs();
    this.setDirAges();
    this.setFilteredDirs();
    this.setAnglesAndDistancesOfPlayers();
    this.filterPlayersForDirs();

    // the goalie is only allowed a subset of directions to avoid dangerous
    // passes to the center
    if (this.getWorld().getSelfRef().isGoalie()) {
      this.filterGoalieDirs();
    }

    this.setPlayerOffset();
    this.setMaxTurnAngles();

    final int synchroQuarter = this.synchro.getVIArrivalQuarter();

    Player self = this.getWorld().getSelfRef();
    Ball ball = this.getWorld().getBallRef().cloned();
    if (self.tackleSuccessProbability(ball.getPosition())>0.1 && !self.canKick(ball)){
      this.reset();
      this.setPlayerPositionAndSpeed();
      this.subStateString="tackle";
      for (int dir = 0; dir < this.numberPassDirs; dir++) {
        if (this.filteredDirs[dir]) {
          continue;
        }
  
        ball.copy(this.getWorld().getBallRef());
        ball.predictThisAfterAction(self, new TackleAction(360/numberPassDirs*dir));
        this.ballPosition[dir].copy(ball.getPosition());
        this.ballSpeed[dir].copy(ball.getSpeed());
        this.maxBallSpeed[dir].copy(ball.getSpeed());

        bestValue = this.calculateBestPassDir();

        // found new best pass
        if (bestValue > this.benefit) {
          this.benefit = bestValue;
        }
      }
      this.bestAction = new TackleAction(360/numberPassDirs*this.bestDir);
      
      ball.copy(this.getWorld().getBallRef());
      ball.predictThisAfterAction(self, bestAction);
      //System.out.println(this.getWorld().getCycle() + " this.passTo " + this.getPassTo() + " dir : " + 360/numberPassDirs*this.bestDir   + " ballPred  " + ball);
      //this.benefit=0.9;
    }else{
      this.subStateString="kick";
      // find the max ball accelerations for each direction
      for (int dir = 0; dir < this.numberPassDirs; dir++) {
        if (this.filteredDirs[dir]) {
          continue;
        }
  
        accel = this.getWorld().getSelfRef().getMaxBallAccelOptimized(b,
            this.dirs[dir]);
  
        this.maxBallSpeed[dir].copy(this.dirs[dir]);
        this.maxBallSpeed[dir].setLength(accel);
      }
  
      // try to kick with different speed to the chosen direction and calculate
      // the benefit of each pass
      for (int accelIndex = 0; accelIndex < this.accelPercentage.length; accelIndex++) {
  
        // if we don't have time for the calculation try only fast kicking
        if ((synchroQuarter == 2 || this.synchro.isShouldHurry()) && accelIndex > 0) {
          break;
        }
  
        this.reset();
        this.setPlayerPositionAndSpeed();
  
        // set the initial ball speed resulting form the accelPercentage
        for (int dir = 0; dir < this.numberPassDirs; dir++) {
          if (this.filteredDirs[dir]) {
            continue;
          }
  
          accel = this.maxBallSpeed[dir].getLength() * this.accelPercentage[accelIndex];
  
          // if the resulting ball speed is to slow we don't pass
          if (accel < 0.4) {
            this.considerDir[dir] = false;
          }
          else {
            this.ballPosition[dir].copy(b.getPosition());
            this.ballSpeed[dir].copy(this.maxBallSpeed[dir]);
            this.ballSpeed[dir].multThis(this.accelPercentage[accelIndex]);
          }
        }
  
        bestValue = this.calculateBestPassDir();

        // found new best pass
        if (bestValue > this.benefit) {
          this.benefit = bestValue;
          bestAccelPercentage = this.accelPercentage[accelIndex];
        }
      }
      if (this.passTo == this.emptyInt) {
        return;
      }

      // set the resulting ball
      this.resBall.setSpeed(this.dirs[this.bestDir]);
      this.resBall.getSpeed().multThis(
          this.getWorld().getSelfRef().getMaxBallAccelOptimized(b,
              this.dirs[this.bestDir]) * bestAccelPercentage);

      // dummyVektor contains best accel vector
      this.dummyVektor.copy(this.dirs[this.bestDir]);
      this.dummyVektor
          .setLength(this.getWorld().getSelfRef().getMaxBallAccelOptimized(b,
              this.dirs[this.bestDir]) * bestAccelPercentage);

      this.bestAction = BasicActionFactory.accelerateBallToVelocity(this
          .getWorld().getSelfRef(), b, this.dummyVektor);

      // once found try to optimize the pass
      if (this.passTo != this.emptyInt) {
        this.correctedPass();
      }
    
    }


  }

  /**
   * sets the age for each direction
   */
  private void setDirAges() {

    for (int dir = 0; dir < this.dirAge.length; dir++) {
      this.dirAge[dir] = this.getWorld().getAgeOfDir(this.dirAngles[dir]);
    }
  }

  /**
   * passing is very time consuming so if we are running on a slow machine and
   * we already had some holes when with ball we reduce the number of pass dirs.
   */
  private void setFilteredDirs() {

    if (this.getWorld().getPlayMode() == PLAY_MODE.KICK_OFF_OWN) {
      for (int dir = 0; dir < this.filteredDirs.length; dir++) {
        if (Math.abs(this.dirAngles[dir]) < 90) {
          this.filteredDirs[dir] = true;
        }
        else {
          this.filteredDirs[dir] = false;
        }
      }
      return;
    }

    final double holePercentage = this.synchro.getWithBallHolePercentage();

    final int synchroQuarter = this.synchro.getVIArrivalQuarter();
    double usedDirPercentage = 1.0;

    if (holePercentage > 0.05) {
      usedDirPercentage /= 2.0;
    }
    else if (holePercentage > 0.01) {
      usedDirPercentage /= 1.3;
    }
    else {
      usedDirPercentage /= 1.0;
    }

    switch (synchroQuarter) {
      case 0:
        break;

      case 1:
        usedDirPercentage /= 2.0;
        break;

      case 2:
        usedDirPercentage /= 5.0;
        break;

      default:
        usedDirPercentage /= 10.0;
        break;
    }

    final int mod = (int) (this.numberPassDirs * usedDirPercentage);

    for (int dir = 0; dir < this.numberPassDirs; dir++) {
      if (dir % mod == 0) {
        this.filteredDirs[dir] = true;
      }
      else {
        this.filteredDirs[dir] = false;
      }
    }
  }


	/**
	 * optimizes the speed of the pass when the pass is secure.
	 */
	private void correctedPass() {
    //only needed when the ball arrives very fast and only done when no
    //opponent is near
		if (this.resBall.getSpeed().getLength()
				* Ball.ballDecayAfterNCycles(this.resInterceptionTime) < 1
				|| ((this.resNearestOpponentDistance < 1.0 
            || this.resDistanceOpponent < 1) && this.resInterceptionTime > 4)
				|| this.resDistanceOwn > 2 + this.getPlayer(this.passTo)
						.getAge(this.getWorld().getBodyCycle(),
								UPDATE_MODE.META_BASED_ON_INFO)) {
			return;
		}


    // slow down the ball by this number of cycles
    int delay = this.resNearestOpponentDistance > 3 ? 1 : 0;

    if (this.resBall.getSpeed().getLength() > 1.0) {
      delay += 1;
    }

    // more delay if the receiver wasn't seen lately
    if (this.getPlayer(this.passTo).getAge(this.getWorld().getBodyCycle(),
        UPDATE_MODE.META_BASED_ON_INFO) > 2) {
      delay += 1;
      delay += this.getPlayer(this.passTo).getAge(
          this.getWorld().getBodyCycle(), UPDATE_MODE.META_BASED_ON_INFO) % 5;
    }

    // more delay if the interception angle is near 90 degree as in this case
    // the chance that the receiver misses the ball is higher
    final double interceptAngle = this.getPlayer(this.passTo).getAngleTo(this.resIP);

    if (Math
        .abs(Math.abs(Vektor
            .normalize(180.0 + interceptAngle + this.dirAngles[this.bestDir])) - 90.0) < 30) {
      delay += 1;
    }

    delay = Math.min(
        (int) this.resDistanceOpponent - 1 + (this.resInterceptionTime % 5),
        delay);
    delay = Math.min(3, delay);

    // get the delayed kickaction and set the best pass data
    final KickAction kA = BasicActionFactory.kickInTime(this.getWorld().getSelfRef(),
        this.getWorld().getBallRef(), this.resIP,
        this.resInterceptionTime + delay, 0);

    if (kA != null) {
      this.resBall.copy(this.getWorld().getBallRef());
      this.resBall.predictThisAfterAction(this.getWorld().getSelfRef(), kA);
      this.resBall.getPosition().copy(
          this.getWorld().getBallRef().getPosition());
      this.resBall.getSpeed().multThis(1.0 / SConf.getInstance().BALL_DECAY);

      this.bestAction = kA;
    }
  }

  /**
   * simulate passing after values are set.
   * 
   * simulates the ball movement for all directons and calculates who intercepts
   * first.
   */
  private void simulate() {

    // we kicked the ball
    this.extrapolateBalls();

    // and drifted once
    this.extrapolatePlayerPosition();

    // the distnce extrapolated player to extrapolated ball
    double distance;

    // the percentage of perfect player movement
    double imperfectPercentage = 1.0;

    // for all cycles
    for (int cycle = 1; cycle <= this.maxCycles; cycle++) {

      // add some noise
      if (cycle > 10) {
        imperfectPercentage = 0.9;
      }
      else if (cycle > 4) {
        imperfectPercentage = 0.95;
      }

      // and all directions
      for (int dir = 0; dir < this.numberPassDirs; dir++) {

        // dir is already out of game
        if (!this.considerDir[dir]) {
          continue;
        }

        // don't make distant backward passes
        if (cycle > 20 && (Math.abs(this.dirAngles[dir]) > 120)) {
          this.considerDir[dir] = false;
          continue;
        }

        // ball is outside -> don't consider dir
        if (!this.getWorld().inField(this.ballPosition[dir])) {
          this.considerDir[dir] = false;
          continue;
        }

        this.interceptDistanceOpponent[dir] = Integer.MAX_VALUE;
        this.interceptDistanceOwn[dir] = Integer.MAX_VALUE;

        // for all players
        for (int playerIndex = 0; playerIndex < this.expectedPlayers.length; playerIndex++) {
          // dir was set to false so don't consider it
          if (!this.considerDir[dir] && playerIndex < 11) {
            continue;
          }

          if (!this.considerPlayers[dir][playerIndex]) {
            continue;
          }

          // only opponents if a teammate already intercepted
          if (this.interceptTimes[dir] != this.emptyInt && playerIndex < 11) {
            continue;
          }

          int j = cycle + this.playerOffset[playerIndex];
          if (j < 0) {
            continue;
          }

          j = Math.min(this.maxCycles - 1, j);

          // get the distance
          distance = this.getMovements().getLeftDistance(playerIndex, j,
              this.ballPosition[dir], this.playerPosition[playerIndex],
              this.bodyDirs[playerIndex], this.maxTurnAngle[playerIndex][0],
              this.maxTurnAngle[playerIndex][1],
              this.secureDistance[playerIndex], imperfectPercentage);

          if (playerIndex < 11) {
            if (distance < this.interceptDistanceOwn[dir]) {
              this.interceptDistanceOwn[dir] = distance;
            }
          }
          else {
            distance -= 0.15;

            if (distance < this.interceptDistanceOpponent[dir]) {
              this.interceptDistanceOpponent[dir] = distance;
            }
            if (distance < this.nearestOpponenentDistance[dir]) {
              this.nearestOpponenentDistance[dir] = distance;
            }
          }

          // someone intercepts -> remember this player
          if (distance < 0) {
            this.finalizeDir(cycle, dir, playerIndex);
            continue;
          }
          else {
            continue;
          }
        }
      }

      // extrapolate ball and players
      this.extrapolateBalls();
      this.extrapolatePlayerPosition();
    }
  }

  /**
   * an intercepting player was found so we have to finalize the direction.
   * 
   * @param cycle -
   *          the interception time
   * @param dir -
   *          the pass direction
   * @param player -
   *          the receiver
   */
  private void finalizeDir(final int cycle,
      final int dir,
      final int player) {

    // first intercepting player
    if (this.interceptTimes[dir] == this.emptyInt) {
      this.considerDir[dir] = false;

      // don't pass to opponents or to the goalie.
      if (player > 10 || player == 0 || this.isFilteredTeammate[player] || (this
          .getWorld().getSelfRef().isGoalie() && this.getWorld().getPlayMode() == PLAY_MODE.GOAL_KICK_OWN && this
          .getWorld().inOwnPenaltyArea(this.ballPosition[dir].x,
              this.ballPosition[dir].y, 1.0))) {
        return;
      }

      this.interceptTimes[dir] = cycle;
      this.interceptingPlayer[dir] = player;
      this.interceptPoints[dir].copy(this.ballPosition[dir]);
    }
  }

  /**
   * choose the best pass direction. this method evaluates every possible pass
   * and sets the pass with the highest benefit.
   * 
   */
  private double calculateBestPassDir() {

    this.simulate();

    double maxValue = this.benefit;
    double value;
    int security;

    // for each dir
    for (int dir = 0; dir < this.dirs.length; dir++) {
      if (this.filteredDirs[dir]) {
        continue;
      }

      // interception by teammate
      if (this.interceptingPlayer[dir] < 11) {

        final double distanceOpp = this.interceptDistanceOpponent[dir];

        if (distanceOpp < 0) {
          security = 0;
        }
        else {
          security = Math.min(5, (int) (distanceOpp / 1.2) + 1);
        }

        // team left for receiver before opponent gets near the interception
        // point
        final int securityDelay = (int) (this.getPlayer(this.interceptingPlayer[dir])
            .getAge(this.getWorld().getBodyCycle(),
                UPDATE_MODE.META_BASED_ON_INFO) / 2.0 + this.dirAge[dir] / 2.0 + 0.5);

        security = Math.max(security - securityDelay, 0);

        // the benefit for the situation resulting when passing
        value = this.getSituationEvaluator().getValue(SituationEvaluator.TYPE.PASSING,
            this.getWorld().getBallRef().getPosition(),
            this.interceptPoints[dir], security, this.interceptTimes[dir], this
                .getPlayer(this.interceptingPlayer[dir]));

        // if a better pass was found, store it
        if (value > maxValue) {
          value = this.correctValueByOutFact(value, this.bestDir);
          value = this.correctValueByNearestOpponentFact(value, this.bestDir);
          if (value > maxValue) {
            maxValue = value;
            this.resIP.copy(this.interceptPoints[dir]);
            this.passTo = this.interceptingPlayer[dir];
            this.bestDir = dir;
            this.resDistanceOpponent = this.interceptDistanceOpponent[dir];
            this.resDistanceOwn = this.interceptDistanceOwn[dir];
            this.resNearestOpponentDistance = this.nearestOpponenentDistance[dir];
            this.resInterceptionTime = this.interceptTimes[dir];
          }
        }
      }
      else {
      }
    }
    return maxValue;
  }

  /**
   * corrects a pass value when the pass might go out.
   * 
   * @param value -
   *          previous value
   * @param bestDir -
   *          bestDir index
   * @return the corrected value (same as the value when no danger)
   */
  private double correctValueByOutFact(final double value,
      final int bestDir) {

    this.dummyVektor.copy(this.ballPosition[bestDir]);

    for (int i = 1; i < 4; i++) {
      this.dummyVektor.addToThis(this.ballSpeed[bestDir]);
      if (!this.getWorld().inField(this.dummyVektor)) {
        return value * i / 4;
      }
    }

    return value;
  }

  /**
   * corrects the pass value if an opponent gets very near the pass line
   * 
   * @param value -
   *          the benefit
   * @param bestDir -
   *          the best direction
   * @return the corrected benefit
   */
  private double correctValueByNearestOpponentFact(final double value,
      final int bestDir) {

    if (this.nearestOpponenentDistance[bestDir] < 0.5) {
      return value * 0.8;
    }

    return value;
  }

  /**
   * extrapolates all balls by 1 cycle.
   * 
   */
  private void extrapolateBalls() {

    for (int dir = 0; dir < this.ballPosition.length; dir++) {
      if (!this.filteredDirs[dir] && this.considerDir[dir]) {
        this.ballPosition[dir].addToThis(this.ballSpeed[dir]);
        this.ballSpeed[dir].multThis(0.94);
      }
    }
  }

  /**
   * the player moves also without dashing (drift). This method accumulates this
   * drifting and sets the players new position and speed.
   * 
   */
  private void extrapolatePlayerPosition() {

    for (int i = 0; i < this.playerPosition.length; i++) {

      this.playerPosition[i].addToThis(this.playerSpeed[i]);
      this.playerSpeed[i].multThis(this.expectedPlayers[i].getPConf().PLAYER_DECAY);
    }
  }

  /**
   * sets values for distances and angle to/of players
   */
  private void setAnglesAndDistancesOfPlayers() {

    for (int i = 0; i < 22; i++) {
      if (this.getPlayer(i).isMe()) {
        this.angleToPlayer[i] = 0;
        this.distanceToPlayer[i] = 0;
      }
      else {
        this.angleToPlayer[i] = this.getWorld().getSelfRef().getAngleTo(
            this.getPlayer(i));
        this.distanceToPlayer[i] = this.getWorld().getSelfRef().getDistance(
            this.getPlayer(i));
      }
    }

  }

  /**
   * sets flags that tell which player has to be considered for which pass
   * direction (used only to improve performance)
   */
  private void filterPlayersForDirs() {

    double angleDiff;
    double dirAngle;

    // for all dirxPlayer combinations
    for (int dir = 0; dir < this.dirs.length; dir++) {
      if (this.filteredDirs[dir]) {
        continue;
      }

      dirAngle = this.dirAngles[dir];

      for (int player = 0; player < 22; player++) {
        angleDiff = Math.abs(Vektor
            .normalize(dirAngle - this.angleToPlayer[player]));
        if ((angleDiff > PassSituation.MAX_TOLERATED_ANGLE_TO_PLAYER && this.distanceToPlayer[player] > PassSituation.MAX_NEAR_DISTANCE_TO_PLAYER) || !this
            .getPlayer(player).isReliable()) {
          this.considerPlayers[dir][player] = false;
        }
        else {
          this.considerPlayers[dir][player] = true;
        }
      }
    }
  }

  /**
   * sets the dirs. the density is higher in goal direction.
   * 
   */
  private void setDirs() {

    final double dirToGoal = this.getWorld().getSelfRef().getAngleTo(
        SConf.getInstance().GOAL_POS_OTHER);
    final boolean iAmUnderGoal = dirToGoal < 0;
    // if the angle to goal is high the player is in a corner of the field
    // this factor helps to prefer directions in field dir
    final int offsetFactor = (int) dirToGoal / 30;
    int dirCounter = 0;

    // the number of directions for all slices of 45° is set
    for (int i = 0; i < 4; i++) {
      this.numberDirsLeft[i] = (int) (this.dirImportance[i] * this.numberPassDirs);
      this.numberDirsRight[i] = (int) (this.dirImportance[i] * this.numberPassDirs);
      dirCounter += this.numberDirsLeft[i] + this.numberDirsRight[i];

      this.numberDirsLeft[i] -= offsetFactor;
      this.numberDirsRight[i] += offsetFactor;

      // correct if negativ - left
      if (this.numberDirsLeft[i] < 0) {
        this.numberDirsRight[i] += this.numberDirsLeft[i];
        this.numberDirsLeft[i] = 0;
      }
      // correct if negativ - right
      if (this.numberDirsRight[i] < 0) {
        this.numberDirsLeft[i] += this.numberDirsRight[i];
        this.numberDirsRight[i] = 0;
      }
    }

    // if directions were left use them in most important slice
    while (dirCounter < this.numberPassDirs) {
      if (iAmUnderGoal) {
        this.numberDirsLeft[0]++;
      }
      else {
        this.numberDirsRight[0]++;
      }
      dirCounter++;
    }

    double intervalSize;
    double intervalCounter;

    int part = 0;
    dirCounter = 0;

    // right side -> clockwise from 0 to 180
    while (part < 4) {
      if (this.numberDirsRight[part] == 0) {
        part++;
        continue;
      }

      // angle between 2 directions
      intervalSize = (double) 45.0 / this.numberDirsRight[part];
      intervalCounter = 0;
      while (this.numberDirsRight[part] > 0) {
        this.dirs[dirCounter].pointAtPolar(1.0,
            dirToGoal + part * 45.0 + intervalSize * intervalCounter);
        this.dirAngles[dirCounter] = Vektor
            .normalize(dirToGoal + part * 45.0 + intervalSize * intervalCounter);
        this.numberDirsRight[part]--;
        dirCounter++;
        intervalCounter++;
      }
      part++;
    }

    part = 3;

    // left side -> clockwise from -180 to 0
    while (part > -1) {
      if (this.numberDirsLeft[part] == 0) {
        part--;
        continue;
      }

      intervalSize = (double) 45.0 / this.numberDirsLeft[part];
      intervalCounter = 0;
      while (this.numberDirsLeft[part] > 0) {
        this.dirs[dirCounter]
            .pointAtPolar(
                1.0,
                dirToGoal + 180 + (3 - part) * 45.0 + intervalSize * intervalCounter);
        this.dirAngles[dirCounter] = Vektor
            .normalize(dirToGoal + 180 + (3 - part) * 45.0 + intervalSize * intervalCounter);
        this.numberDirsLeft[part]--;
        dirCounter++;
        intervalCounter++;
      }
      part--;
    }
  }

  /**
   * the goalie isn't allowed to pass in the middle
   * 
   * @param goaliePos
   */
  private void filterGoalieDirs() {

    if (!this.getWorld().inOwnPenaltyArea(this.getWorld().getSelfRef())) {
      return;
    }

    boolean left = this.getWorld().getSelfRef().getPosition().x < 0;

    for (int i = 0; i < this.dirs.length; i++) {
      if ((left && this.dirAngles[i] > 0) || (!left && this.dirAngles[i] < 0)) {
        this.considerDir[i] = false;
      }
      else {
        this.considerDir[i] = true;
      }
    }
  }

  /**
   * players that were not seen might be nearer than expected. own players need
   * one cycle to react.
   * 
   */
  private void setPlayerOffset() {

    int j = 0;
    for (int playerIndex = 0; playerIndex < this.expectedPlayers.length; playerIndex++) {
      j = 0;
      if (playerIndex < 11) {
        if (this.expectedPlayers[playerIndex].getAge(this.getWorld()
            .getBodyCycle(), UPDATE_MODE.META_BASED_ON_INFO) == 0) {
          j -= 1;
        }
        else {
          j -= 2;
        }
      }
      else {
        j += Math.min(3, this.expectedPlayers[playerIndex].getAge(this
            .getWorld().getBodyCycle(), UPDATE_MODE.META_BASED_ON_INFO));
      }
      j -= this.getPlayer(playerIndex).getTackledeadCountdown();
      this.playerOffset[playerIndex] = j;
    }
  }

  /**
   * set the max turn angles for every player based on the players speed. (used
   * for efficiency)
   * 
   */
  private void setMaxTurnAngles() {

    for (int playerIndex = 0; playerIndex < this.expectedPlayers.length; playerIndex++) {
      this.maxTurnAngle[playerIndex][0] = this.getPlayer(playerIndex)
          .getMaxTurningAngle1();
      this.maxTurnAngle[playerIndex][1] = this.getPlayer(playerIndex)
          .getMaxTurningAngle2();
    }
  }

  /**
   * sets the position and speed arrays for each player. (used for efficiency)
   * 
   */
  private void setPlayerPositionAndSpeed() {

    for (int i = 0; i < this.expectedPlayers.length; i++) {
      this.playerPosition[i].copy(this.expectedPlayers[i].getPosition());
      this.playerSpeed[i].copy(this.expectedPlayers[i].getSpeed());
    }
  }

  /**
   * playerIndex -> player
   * 
   * @param index
   * @return
   */
  private Player getPlayer(final int index) {

    return this.expectedPlayers[index];
  }

  /**
   * found someone to pass to.
   * 
   * @return true if passin is possible
   */
  public boolean canPass() {

    if (!this.wasUpdated()) {
      this.update();
    }
//    if (PLAY_MODE.META_STANDARD_OWN.contains(getWorld().getPlayMode()))
//    	System.out.println("CAN-PASS: " + (this.passTo != this.emptyInt));
    return this.passTo != this.emptyInt;
  }

  /**
   * @return Returns the receivers number. Integer.MAX_VALUE if no passing
   *         possible
   */
  public Player getPassTo() {

    if (!this.wasUpdated()) {
      this.update();
    }
    if (this.passTo == Integer.MAX_VALUE) {
      return null;
    }

    return this.getPlayer(this.passTo);
  }

  /**
   * @return Returns the resulting ip.
   */
  public Vektor getResIP() {

    if (!this.wasUpdated()) {
      this.update();
    }
    return this.resIP;
  }

  /**
   * the ball that results from passing.
   * 
   * @return the resulting ball
   */
  public Ball getResBall() {

    if (!this.wasUpdated()) {
      this.update();
    }
    return this.resBall;
  }
}
